﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using AccessControl.HardwareLibrary.EnumAndStruct;
using AccessControl.Utility.Tools;
using System.Text.RegularExpressions;
using System.Reflection;

namespace AccessControl.HardwareLibrary.Gate
{
    public class GateCommunicator : Device
    {
        #region Settings

        public bool Use10BytesCardID { get; set; }

        private static int _commandTimeout = 5000;
        public static int CommandTimeout
        {
            get { return _commandTimeout; }
            set { _commandTimeout = value; }
        }

        private static int _commandFailRetries = 3;
        public int CommandFailRetries
        {
            get { return _commandFailRetries; }
            set { _commandFailRetries = value; }
        }

        private static bool _cancelCommandIfReconnect = true;
        public static bool CancelCommandIfReconnect
        {
            get { return _cancelCommandIfReconnect; }
            set { _cancelCommandIfReconnect = value; }
        }

        #endregion

        #region Enumerations
        private enum ResponseTypeConstants
        {
            // ReSharper disable UnusedMember.Local
            Test = 11,
            SetServerTimeout = 30,
            BuzzerEnable = 35,
            SetEntranceTimeout = 33,
            BuzzerRun = 36,
            OpenGate = 21,
            Buzz = 37,
            // ReSharper restore UnusedMember.Local
            CardReport = 22,
            CustomEntranceReport = 21
        }
        #endregion

        #region fields
        private readonly Queue<string> sendBuffer;
        private Timer tmrTimeout;
        private bool isBusy, isBeeping;
        private TcpClient client;
        private Thread clientThread;
        private NetworkStream clientStream;
        #endregion

        #region Properties

        public override string DeviceType
        {
            get { return "Gate"; }
        }

        public bool AutoReconnect { get; set; }
        //private int _timeout = 10000;
        //public int Timeout
        //{
        //    get { return _timeout = 10000; }
        //    set { _timeout = value; }
        //}

        #endregion

        #region Events

        public event Action<object, string> DataReceived;
        public void OnDataReceived(string data)
        {
            if (DataReceived != null) DataReceived(this, data);
        }

        public event Action<object, string> DataSent;
        public void OnDataSent(string data)
        {
            if (DataSent != null) DataSent(this, data);
        }

        public event Action<object, string> CardDetected;
        public void OnCardDetected(string cardID)
        {
            if (CardDetected != null) CardDetected(this, cardID);
        }

        public event Action<object, string> Entered;
        public void OnEntered(string cardID)
        {
            if (Entered != null) Entered(this, cardID);
        }

        public event Action<object, string> NotEntered;
        public void OnNotEntered(string cardID)
        {
            if (NotEntered != null) NotEntered(this, cardID);
        }

        public event EventHandler ServerResponseTimeoutExpired;
        public void OnServerResponseTimeoutExpired()
        {
            if (ServerResponseTimeoutExpired != null) ServerResponseTimeoutExpired(this, EventArgs.Empty);
        }

        #endregion

        #region Constructors

        public GateCommunicator()
        {
            sendBuffer = new Queue<string>();
        }

        public GateCommunicator(string ipString, int port, int failRetries)
            : this()
        {
            Connect(ipString, port, failRetries);
        }

        #endregion

        #region Basic Methods
        public override sealed bool Connect(string ipString, int port, int failRetries)
        {
            AutoReconnect = true;

            //if (client!=null && client.Connected)
            //    throw new ApplicationException("Gate is already connected!");

            try
            {
                IP = new IPEndPoint(IPAddress.Parse(ipString), port);

                var retries = 0;
                while (!Connect(IP))
                {
                    if (failRetries == -1 || ++retries < failRetries)
                    {
                        Thread.Sleep(3000);
                        continue;
                    }
                    if (failRetries >= 0) WriteLog("Unable to connect to device after " + retries + " attempt(s)!", eventLevelConstants.Error);
                    return false;
                }

                clientThread = new Thread(Receive);
                clientThread.Start();

                return true;
            }
            catch (Exception)
            {
                WriteLog("Error connecting to device!", eventLevelConstants.Error);
                return false;
            }
        }
        public bool Connect(IPEndPoint ipEndPoint)
        {
            try
            {
                client = new TcpClient();
                
                client.Connect(ipEndPoint);

                clientStream = client.GetStream();

                OnConnectionChanged(true);
                WriteLog("Connected!", eventLevelConstants.Info);
                return true;
            }
            catch (Exception)
            {
                WriteLog("Error connecting to device!", eventLevelConstants.Error);
                return false;
            }

        }

        public void Reconnect()
        {
            if (client.Connected) client.Close();

            Connect(IP);
        }

        public bool CheckConnection()
        {
            if (!AutoReconnect) return false;

            if(!client.Connected) Reconnect();

            return true;
        }

        public override void Disconnect()
        {
            AutoReconnect = false;
            if (client.Connected) client.Close();

            OnConnectionChanged(false);
        }

        private void Receive()
        {

            var receiveBuffer = new byte[client.Client.ReceiveBufferSize];
            var Data = "";
            client.NoDelay = true;

            while (AutoReconnect)
            {

                try
                {
                    while (client.Connected)
                    {

                        var availableDataSize = clientStream.Read(receiveBuffer, 0, client.Available);
                        Data += Encoding.ASCII.GetString(receiveBuffer, 0, availableDataSize);

                        if (!Data.EndsWith("]")) continue;

                        System.Diagnostics.Debug.WriteLine("r" + Data);
                        Data = Data.Substring(Data.IndexOf("["));
                        var commands = new Regex("\\[\\w+\\]").Matches(Data);
                        OnDataReceived(Data);
                        Data = "";


                        //isBusy = false;
                        if (sendBuffer.Count > 0){
                            System.Diagnostics.Debug.WriteLine("receive+send" + sendBuffer.Peek());
                            SendFirstInQueue();
                        }
                        else{
                            System.Diagnostics.Debug.WriteLine("isbusy=false");
                            isBusy = false;
                        }

                        foreach (Match m in commands)
                        {


                            AnalyzeResponse(m.Value);
                        }

                        


                    }

                }
                // ReSharper disable EmptyGeneralCatchClause
                catch { }
                // ReSharper restore EmptyGeneralCatchClause
                Thread.Sleep(1000);
                CheckConnection();
            }
        }
        private string getCardNumber(string data){
            return Use10BytesCardID ? RFID.RfID14To10(data) : data;
        }

        private void AnalyzeResponse(string responseString){
            string data;
            if (!responseString.StartsWith("[") || !responseString.EndsWith("]"))
                return;

            var responseType = (ResponseTypeConstants)int.Parse(responseString.Substring(1, 2));




            switch (responseType)
            {
                case ResponseTypeConstants.CardReport: //22
                    var Entered=false;
                    var DataSize = int.Parse(responseString.Substring(3, 2));
                    if (DataSize > 0 && responseString.Length >= 20)
                        data = responseString.Substring(5, DataSize);
                    else data = "";

                    if (DataSize==14)
                        OnCardDetected(getCardNumber(data));
                    else if(data.Length==15){
                        Entered = data.Substring(data.Length - 1, 1) == "1" ? true : false;
                        data = data.Substring(0, data.Length - 1);

                        if (Entered) OnEntered(getCardNumber(data));
                        else OnNotEntered(getCardNumber(data));
                    }
                    break;
                case ResponseTypeConstants.CustomEntranceReport: //21
                    data = responseString.Substring(3, 1);
                    Entered = data == "1" ? true : false;
                    if (Entered) OnEntered("");
                    else OnNotEntered("");
                    break;
                //case ResponseTypeConstants.Test: //01
                //case ResponseTypeConstants.SetServerTimeout: //04
                //case ResponseTypeConstants.BuzzerEnable: //05
                //case ResponseTypeConstants.SetEntranceTimeout: //06
                //case ResponseTypeConstants.BuzzerRun: //07
                //case ResponseTypeConstants.OpenGate: //08
                //case ResponseTypeConstants.Buzz: //10
                //    break;
            }


            return;
        }

        public void Send(string command)
        {
            if (!CheckConnection()) return;

            sendBuffer.Enqueue(command);
            if (!isBusy){
                System.Diagnostics.Debug.WriteLine("sending" + command);
                SendFirstInQueue();
            }
            else
            {
                //tmrTimeout = new Timer(new TimerCallback(delegate(){ isBusy = false;SendFirstInQueue(); }),);
                //SendFirstInQueue.in
                System.Diagnostics.Debug.WriteLine("queue" + command);
            }

        }

        private void SendFirstInQueue()
        {
            try
            {
                var cmd = sendBuffer.Dequeue();

                //if (!cmd.StartsWith("[10"))
                    isBusy = true;

                var data = Encoding.ASCII.GetBytes(cmd);
                OnDataSent(cmd);
                clientStream.Write(data, 0, data.Length);
            }
            catch (Exception)
            {
                WriteLog("Couldnt send data to gate!", eventLevelConstants.Error);
            }

        }

        #endregion

        #region Commands

        public void AllowGateForCard()
        {
            Send("[22011]");
        }


        public void AllowGateCustom()
        {
            Send("[21011]");
        }


        public void DenyGateForCard()
        {
            Send("[22010]");
        }

        public void SetEntranceTimeout(int milliseconds)
        {
            milliseconds = Math.Min(milliseconds, 9999);
            Send("[3302" + (milliseconds / 1000).ToString("00") + "]");
        }

        public void SetServerResponseTimeout(int milliseconds)
        {
            milliseconds = Math.Min(milliseconds, 9999);
            Send("[3002" + (milliseconds / 100).ToString("00") + "]");
        }

        public void Test()
        {
            Send("[1100]");
        }

        public void SetDefaultBeepsOn()
        {
            Send("[35011]");
        }

        public void SetDefaultBeepsOff()
        {
            Send("[35010]");
        }

        public void SetBuzzerOn()
        {
            Send("[36011]");
        }

        public void SetBuzzerOff()
        {
            Send("[36010]");
        }

        private void Beep(object beepType)
        {
            Beep((Beeps)beepType);
        }

        public void Beep(Beeps beepType)
        {
            while (isBeeping && isBusy) Thread.Sleep(100);

            isBeeping = true;

            var repeats = 0;
            var duration = 30;
            var delay = 30;
            switch (beepType)
            {
                case Beeps.Normal:
                    repeats = 1;
                    duration = 50;
                    break;
                case Beeps.DoubleShort:
                    repeats = 2;
                    delay = 50;
                    duration = 70;
                    break;
                case Beeps.DoubleLong:
                    repeats = 2;
                    delay = 200;
                    duration = 300;
                    break;
                case Beeps.Triple:
                    repeats = 3;
                    delay = 80;
                    duration = 80;
                    break;
                case Beeps.TestLong:
                    repeats = 4;
                    delay = 1000;
                    duration = 100;
                    break;
            }

            Send("[3705" + repeats.ToString("00") + (duration / 10).ToString("000") + "]");

            isBeeping = false;
        }

        public void Beep2(Beeps beepType)
        {
            while (isBeeping && isBusy) Thread.Sleep(100);

            isBeeping = true;

            var repeats = 0;
            var duration = 30;
            var delay = 30;
            switch (beepType)
            {
                case Beeps.Normal:
                    repeats = 1;
                    duration = 50;
                    break;
                case Beeps.DoubleShort:
                    repeats = 2;
                    delay = 50;
                    duration = 50;
                    break;
                case Beeps.DoubleLong:
                    repeats = 2;
                    delay = 200;
                    duration = 300;
                    break;
                case Beeps.Triple:
                    repeats = 3;
                    delay = 80;
                    duration = 50;
                    break;
                case Beeps.TestLong:
                    repeats = 4;
                    delay = 1000;
                    duration = 100;
                    break;
            }
            for (var r = 1; r <= repeats; r++)
            {
                SetBuzzerOn();
                Thread.Sleep(duration);
                SetBuzzerOff();
                if (r < repeats) Thread.Sleep(delay);
            }

            isBeeping = false;
        }


        #endregion



    }
}
